<?php

// @formatter:off
/**
 * @file WCReadRate.php
 * @author Alejandro Dario Simi
 * @date $Date: 2014-06-03 03:40:32 +0000 (Tue, 03 Jun 2014) $
 *
 * $Id: WCReadRate.php 112 2014-06-03 03:40:32Z daemonraco@gmail.com $
 * $URL: http://wcomix.googlecode.com/svn/trunk/includes/WCReadRate.php $
 */
// @formatter:on

/**
 * @class WCReadRate This class holds all the necessary logic to manage comic
 * ratings and read/unread markers. It also manages some related statistical
 * values on each comic.
 */
class WCReadRate extends WCSingleton {
	/**
	 * @var WCConnection Database singleton shortcut.
	 */
	protected $_db;
	/**
	 * @var string Database tables prefix shortcut.
	 */
	protected $_dbprefix;
	/**
	 * @var WCGroup Current group shortcut.
	 */
	protected $_group;
	/**
	 * @var WCUser Current user shortcut.
	 */
	protected $_user;
	/**
	 * Class constructor.
	 */
	protected function __construct() {
		//
		// Loading global pointers.
		global $wcDatabaseAccess;
		global $wcGroup;
		global $wcUser;
		//
		// Loading shortcuts.
		// @{
		$this->_db = WCConnection::Instance();
		$this->_dbprefix = $wcDatabaseAccess["prefix"];
		$this->_group = $wcGroup;
		$this->_user = $wcUser;
		// @}
	}
	//
	// Public methods.
	/**
	 * This method allows to get a comic's global rate.
	 *
	 * @param WCComic $comic Comic to look for.
	 * @return int Returns a value between zero and ten.
	 */
	public function comicRate(WCComic &$comic) {
		//
		// Requesting comic's statistics.
		$out = $this->comicReadRate($comic);
		//
		// Returning comic's rate.
		return $out["rate"];
	}
	/**
	 * This method allows to get a comic's statistics. As result it generates
	 * an associative array with this key:
	 * 	- 'rate': Global average rate.
	 * 	- 'reads': Count of how many users marked it as read.
	 * 	- 'votes': Count of how many users gave their votes.
	 *
	 * @param WCComic $comic Comic to look for.
	 * @return int[string] Return an associative array with statistical
	 * values. On error, every statistical value will be zero.
	 */
	public function comicReadRate(WCComic &$comic) {
		//
		// Checking if the comic has issues.
		if($comic->ok()) {
			//
			// Building results based on comic's statistics.
			$out = array(
				"rate" => $comic->rate,
				"reads" => $comic->reads,
				"votes" => $comic->votes
			);
		} else {
			//
			// At this point, something is wrong with the comic,
			// therefore a result structure with default values is
			// built.
			$out = array(
				"rate" => 0,
				"reads" => 0,
				"votes" => 0
			);
		}
		//
		// Returning results.
		return $out;
	}
	/**
	 * This method allows to get a comic's global reads count.
	 *
	 * @param WCComic $comic Comic to look for.
	 * @return int Returns the count of reads.
	 */
	public function comicReads(WCComic &$comic) {
		//
		// Requesting comic's statistics.
		$out = $this->comicReadRate($comic);
		//
		// Returning comic's reads.
		return $out["reads"];
	}
	/**
	 * This method allows to get a image's global rate.
	 *
	 * @param WCImage $image Image to look for.
	 * @return int Returns a value between zero and ten.
	 */
	public function imageRate(WCImage &$image) {
		//
		// Requesting image's statistics.
		$out = $this->imageReadRate($image);
		//
		// Returning image's rate.
		return $out["rate"];
	}
	/**
	 * This method allows to get a image's statistics. As result it generates
	 * an associative array with this key:
	 * 	- 'rate': Global average rate.
	 * 	- 'reads': Count of how many users marked it as read.
	 * 	- 'votes': Count of how many users gave their votes.
	 *
	 * @param WCImage $image Image to look for.
	 * @return int[string] Return an associative array with statistical
	 * values. On error, every statistical value will be zero.
	 */
	public function imageReadRate(WCImage &$image) {
		//
		// Checking if the image has issues.
		if($image->ok()) {
			//
			// Building results based on image's statistics.
			$out = array(
				"rate" => $image->rate,
				"reads" => $image->reads,
				"votes" => $image->votes
			);
		} else {
			//
			// At this point, something is wrong with the image,
			// therefore a result structure with default values is
			// built.
			$out = array(
				"rate" => 0,
				"reads" => 0,
				"votes" => 0
			);
		}
		//
		// Returning results.
		return $out;
	}
	/**
	 * This method allows to get a image's global reads count.
	 *
	 * @param WCImage $image Image to look for.
	 * @return int Returns the count of reads.
	 */
	public function imageReads(WCImage &$image) {
		//
		// Requesting image's statistics.
		$out = $this->imageReadRate($image);
		//
		// Returning image's reads.
		return $out["reads"];
	}
	/**
	 * This method allows to get a comic's rate given by certain user.
	 *
	 * @param WCComic $comic Comic to look for.
	 * @param WCUser $user User to use as filter. When 'null' it uses current
	 * user based on '$wcUser'.
	 * @return int Returns a value between zero and ten.
	 */
	public function userComicRate(WCComic &$comic, WCUser &$user = null) {
		//
		// Requesting comic's statistics.
		$out = $this->userComicReadRate($comic, $user);
		//
		// Returning comic's rate.
		return $out["rate"];
	}
	/**
	 * This method allows to get a image's rate given by certain user.
	 *
	 * @param WCImage $image Image to look for.
	 * @param WCUser $user User to use as filter. When 'null' it uses current
	 * user based on '$wcUser'.
	 * @return int Returns a value between zero and ten.
	 */
	public function userImageRate(WCImage &$image, WCUser &$user = null) {
		//
		// Requesting image's statistics.
		$out = $this->userImageReadRate($image, $user);
		//
		// Returning image's rate.
		return $out["rate"];
	}
	/**
	 * @deprecated
	 */
	public function rate(WCComic &$comic, WCUser &$user = null) {
		return $this->userComicRate($comic, $user);
	}
	/**
	 * @deprecated
	 */
	public function readRate(WCComic &$comic, WCUser &$user = null) {
		return $this->userComicReadRate($comic, $user);
	}
	/**
	 * This method retrieves and returns a comic's votes and marks made by
	 * certain user. As result it generates an associative array with this
	 * structure:
	 * 	- 'rate':
	 * 		- 'rate': Given rate between zero and ten.
	 * 		- 'date': Date when the last rate was given.
	 * 	- 'read':
	 * 		- 'read': Read/Unread mark
	 * 		- 'date': Date when the last read/unread mark was given.
	 *
	 * @param WCComic $comic Comic to look for.
	 * @param WCUser $user User to use as filter. When 'null' it uses current
	 * user based on '$wcUser'.
	 * @return boolean Returns true when the comic is marked as read by the
	 * user.
	 */
	public function userComicReadRate(WCComic &$comic, WCUser &$user = null) {
		//
		// Creating a default value to be return.
		$out = array(
			"rate" => array(
				"rate" => false,
				"date" => false
			),
			"read" => array(
				"read" => false,
				"date" => false
			)
		);
		//
		// Creating a shortcut for comic's group.
		$group = $comic->group();
		//
		// Selecting user to use as filter.
		$user = ($user === null) ? $this->_user : $user;
		//
		// Validating user.
		if($user && $user->ok()) {
			//
			// Creating a statement to load comic votes and marks.
			// @{
			$stmt = null;
			$stmtId = __CLASS__."::".__FUNCTION__;
			if($this->_db->has($stmtId)) {
				$stmt = $this->_db->get($stmtId);
			} else {
				$query = "select  crr_rate,\n";
				$query .= "        crr_rate_date,\n";
				$query .= "        crr_read,\n";
				$query .= "        crr_read_date\n";
				$query .= "from    {$this->_dbprefix}rr_comics\n";
				$query .= "where   crr_user  = :user\n";
				$query .= " and    crr_group = :group\n";
				$query .= " and    crr_comic = :comic\n";
				$stmt = $this->_db->prepare($stmtId, $query);
			}
			// @}
			//
			// Building database parameters.
			$params = array(
				":user" => $user->id(),
				":group" => $group->id,
				":comic" => $comic->id
			);
			//
			// Requesting data.
			if($stmt->execute($params) && $stmt->rowCount() > 0) {
				//
				// Fetching data.
				$row = $stmt->fetch();
				//
				// Updating results structure with found data.
				// @{
				$out["rate"]["rate"] = $row["crr_rate"];
				$out["rate"]["date"] = $row["crr_rate_date"];
				$out["read"]["read"] = $row["crr_read"] == "Y";
				$out["read"]["date"] = $row["crr_read_date"];
				// @}
			}
		}
		//
		// Returning results.
		return $out;
	}
	/**
	 * This method retrieves and returns a image's votes and marks made by
	 * certain user. As result it generates an associative array with this
	 * structure:
	 * 	- 'rate':
	 * 		- 'rate': Given rate between zero and ten.
	 * 		- 'date': Date when the last rate was given.
	 * 	- 'read':
	 * 		- 'read': Read/Unread mark
	 * 		- 'date': Date when the last read/unread mark was given.
	 *
	 * @param WCImage $image Image to look for.
	 * @param WCUser $user User to use as filter. When 'null' it uses current
	 * user based on '$wcUser'.
	 * @return boolean Returns true when the image is marked as read by the
	 * user.
	 */
	public function userImageReadRate(WCImage &$image, WCUser &$user = null) {
		//
		// Creating a default value to be return.
		$out = array(
			"rate" => array(
				"rate" => false,
				"date" => false
			),
			"read" => array(
				"read" => false,
				"date" => false
			)
		);
		//
		// Creating a shortcut for image's group.
		$group = $image->group();
		//
		// Selecting user to use as filter.
		$user = ($user === null) ? $this->_user : $user;
		//
		// Validating user.
		if($user && $user->ok()) {
			//
			// Creating a statement to load image votes and marks.
			// @{
			$stmt = null;
			$stmtId = __CLASS__."::".__FUNCTION__;
			if($this->_db->has($stmtId)) {
				$stmt = $this->_db->get($stmtId);
			} else {
				$query = "select  irr_rate,\n";
				$query .= "        irr_rate_date,\n";
				$query .= "        irr_read,\n";
				$query .= "        irr_read_date\n";
				$query .= "from    {$this->_dbprefix}rr_images\n";
				$query .= "where   irr_user  = :user\n";
				$query .= " and    irr_group = :group\n";
				$query .= " and    irr_image = :image\n";
				$stmt = $this->_db->prepare($stmtId, $query);
			}
			// @}
			//
			// Building database parameters.
			$params = array(
				":user" => $user->id(),
				":group" => $group->id,
				":image" => $image->id
			);
			//
			// Requesting data.
			if($stmt->execute($params) && $stmt->rowCount() > 0) {
				//
				// Fetching data.
				$row = $stmt->fetch();
				//
				// Updating results structure with found data.
				// @{
				$out["rate"]["rate"] = $row["irr_rate"];
				$out["rate"]["date"] = $row["irr_rate_date"];
				$out["read"]["read"] = $row["irr_read"] == "Y";
				$out["read"]["date"] = $row["irr_read_date"];
				// @}
			}
		}
		//
		// Returning results.
		return $out;
	}
	/**
	 * This method allows to know if a comic's has been marked as read by
	 * certain user.
	 *
	 * @param WCComic $comic Comic to look for.
	 * @param WCUser $user User to use as filter. When 'null' it uses current
	 * user based on '$wcUser'.
	 * @return boolean Returns true when the comic is marked as read by the
	 * user.
	 */
	public function userComicRead(WCComic &$comic, WCUser &$user = null) {
		//
		// Requesting comic's statistics.
		$out = $this->userComicReadRate($comic, $user);
		//
		// Returning whether the comic's read or not.
		return $out["read"];
	}
	/**
	 * This method allows to know if a image's has been marked as read by
	 * certain user.
	 *
	 * @param WCImage $image Image to look for.
	 * @param WCUser $user User to use as filter. When 'null' it uses current
	 * user based on '$wcUser'.
	 * @return boolean Returns true when the image is marked as read by the
	 * user.
	 */
	public function userImageRead(WCImage &$image, WCUser &$user = null) {
		//
		// Requesting image's statistics.
		$out = $this->userImageReadRate($image, $user);
		//
		// Returning whether the image's read or not.
		return $out["read"];
	}
	/**
	 * @deprecated
	 */
	public function read(WCComic &$comic, WCUser &$user = null) {
		return $this->read($comic, $user);
	}
	/**
	 * @deprecated
	 */
	public function setRate(WCComic &$comic, $rate, WCUser &$user = null) {
		return $this->setComicRate($comic, $rate, $user);
	}
	/**
	 * This method allows to set a rate to a comic.
	 *
	 * @param WCComic $comic Comic that is receiving a rate.
	 * @param int $rate Rate to set.
	 * @param WCUser $user User that is setting the rate.
	 * @return boolean Returns true when the rating was successfully updated.
	 */
	public function setComicRate(WCComic &$comic, $rate, WCUser &$user = null) {
		//
		// Loading global pointers.
		global $wcDefaults;
		//
		// Setting default value to be returned.
		$out = false;
		//
		// Checking that Read Only Mode is not activated.
		if(!$wcDefaults["read-only"]) {
			//
			// Selecting the group related to the rate to set.
			$group = $comic->group();
			//
			// If there's no user specified, the internal user is
			// user.
			$user = ($user === null) ? $this->_user : $user;
			//
			// At this point, there must be a user to use.
			if($user !== false) {
				//
				// Creating a statement to update the rate.
				// @{
				$stmt = null;
				$stmtId = __CLASS__."::".__FUNCTION__;
				if($this->_db->has($stmtId)) {
					$stmt = $this->_db->get($stmtId);
				} else {
					$query = "insert\n";
					$query .= "        into {$this->_dbprefix}rr_comics (\n";
					$query .= "                crr_user, crr_group, crr_comic, crr_rate, crr_rate_date)\n";
					$query .= "        values (:user, :group, :comic, :rate, sysdate())\n";
					$query .= "        on duplicate key\n";
					$query .= "                update  crr_rate      = :rate,\n";
					$query .= "                        crr_rate_date = sysdate()\n";
					$stmt = $this->_db->prepare($stmtId, $query);
				}
				// @}
				//
				// Building database parameters.
				$params = array(
					":user" => $user->id(),
					":group" => $group->id(),
					":comic" => $comic->id(),
					":rate" => $rate
				);
				//
				// Updating rate.
				$stmt->execute($params);
				$comic->rated = false;
				//
				// At this point, operation is considered to be a
				// success.
				$out = true;
			}
		}
		//
		// Returning update status.
		return $out;
	}
	/**
	 * @deprecat	ed
	 */
	public function setRead(WCComic &$comic, $read = true, WCUser &$user = null) {
		return $this->setComicRead($comic, $read, $user);
	}
	/**
	 * This method allows to set a rate to a comic.
	 *
	 * @param WCComic $comic Comic which read status is changing.
	 * @param boolean $read Read status to set.
	 * @param WCUser $user User that is setting the read status.
	 * @return boolean Returns true when the read status was successfully
	 * updated.
	 */
	public function setComicRead(WCComic &$comic, $read = true, WCUser &$user = null) {
		//
		// Loading global pointers.
		global $wcDefaults;
		//
		// Setting default value to be returned.
		$out = false;
		//
		// Checking that Read Only Mode is not activated.
		if(!$wcDefaults["read-only"]) {
			//
			// Selecting the group related to the rate to set.
			$group = $comic->group();
			//
			// If there's no user specified, the internal user is
			// user.
			$user = ($user === null) ? $this->_user : $user;
			//
			// Since read status is a boolean, it must be transformed
			// into a valid string value.
			$crrRead = $read ? "Y" : "N";
			//
			// At this point, there must be a user to use.
			if($user !== false) {
				//
				// Creating a statement to update the read status.
				// @{
				$stmt = null;
				$stmtId = __CLASS__."::".__FUNCTION__;
				if($this->_db->has($stmtId)) {
					$stmt = $this->_db->get($stmtId);
				} else {
					$query = "insert\n";
					$query .= "        into {$this->_dbprefix}rr_comics (\n";
					$query .= "                crr_user, crr_group, crr_comic, crr_read, crr_read_date)\n";
					$query .= "        values (:user, :group, :comic, :read, sysdate())\n";
					$query .= "        on duplicate key\n";
					$query .= "                update  crr_read      = :read,\n";
					$query .= "                        crr_read_date = sysdate()\n";
					$stmt = $this->_db->prepare($stmtId, $query);
				}
				// @}
				//
				// Building database parameters.
				$params = array(
					":user" => $user->id(),
					":group" => $group->id(),
					":comic" => $comic->id(),
					":read" => $crrRead
				);
				//
				// Updating rate.
				$stmt->execute($params);
				$comic->rated = false;
				//
				// At this point, operation is considered to be a
				// success.
				$out = true;
			}
		}
		//
		// Returning update status.
		return $out;
	}
	/**
	 * This method allows to set a rate to a image.
	 *
	 * @param WCImage $image Image that is receiving a rate.
	 * @param int $rate Rate to set.
	 * @param WCUser $user User that is setting the rate.
	 * @return boolean Returns true when the rating was successfully updated.
	 */
	public function setImageRate(WCImage &$image, $rate, WCUser &$user = null) {
		//
		// Loading global pointers.
		global $wcDefaults;
		//
		// Setting default value to be returned.
		$out = false;
		//
		// Checking that Read Only Mode is not activated.
		if(!$wcDefaults["read-only"]) {
			//
			// Selecting the group related to the rate to set.
			$group = $image->group();
			//
			// If there's no user specified, the internal user is
			// user.
			$user = ($user === null) ? $this->_user : $user;
			//
			// At this point, there must be a user to use.
			if($user !== false) {
				//
				// Creating a statement to update the rate.
				// @{
				$stmt = null;
				$stmtId = __CLASS__."::".__FUNCTION__;
				if($this->_db->has($stmtId)) {
					$stmt = $this->_db->get($stmtId);
				} else {
					$query = "insert\n";
					$query .= "        into {$this->_dbprefix}rr_images (\n";
					$query .= "                irr_user, irr_group, irr_image, irr_rate, irr_rate_date)\n";
					$query .= "        values (:user, :group, :image, :rate, sysdate())\n";
					$query .= "        on duplicate key\n";
					$query .= "                update  irr_rate      = :rate,\n";
					$query .= "                        irr_rate_date = sysdate()\n";
					$stmt = $this->_db->prepare($stmtId, $query);
				}
				// @}
				//
				// Building database parameters.
				$params = array(
					":user" => $user->id(),
					":group" => $group->id(),
					":image" => $image->id(),
					":rate" => $rate
				);
				//
				// Updating rate.
				$stmt->execute($params);
				$image->rated = false;
				//
				// At this point, operation is considered to be a
				// success.
				$out = true;
			}
		}
		//
		// Returning update status.
		return $out;
	}
	/**
	 * This method allows to set a rate to a image.
	 *
	 * @param WCImage $image Image which read status is changing.
	 * @param boolean $read Read status to set.
	 * @param WCUser $user User that is setting the read status.
	 * @return boolean Returns true when the read status was successfully
	 * updated.
	 */
	public function setImageRead(WCImage &$image, $read = true, WCUser &$user = null) {
		//
		// Loading global pointers.
		global $wcDefaults;
		//
		// Setting default value to be returned.
		$out = false;
		//
		// Checking that Read Only Mode is not activated.
		if(!$wcDefaults["read-only"]) {
			//
			// Selecting the group related to the rate to set.
			$group = $image->group();
			//
			// If there's no user specified, the internal user is
			// user.
			$user = ($user === null) ? $this->_user : $user;
			//
			// Since read status is a boolean, it must be transformed
			// into a valid string value.
			$crrRead = $read ? "Y" : "N";
			//
			// At this point, there must be a user to use.
			if($user !== false) {
				//
				// Creating a statement to update the read status.
				// @{
				$stmt = null;
				$stmtId = __CLASS__."::".__FUNCTION__;
				if($this->_db->has($stmtId)) {
					$stmt = $this->_db->get($stmtId);
				} else {
					$query = "insert\n";
					$query .= "        into {$this->_dbprefix}rr_images (\n";
					$query .= "                irr_user, irr_group, irr_image, irr_read, irr_read_date)\n";
					$query .= "        values (:user, :group, :image, :read, sysdate())\n";
					$query .= "        on duplicate key\n";
					$query .= "                update  irr_read      = :read,\n";
					$query .= "                        irr_read_date = sysdate()\n";
					$stmt = $this->_db->prepare($stmtId, $query);
				}
				// @}
				//
				// Building database parameters.
				$params = array(
					":user" => $user->id(),
					":group" => $group->id(),
					":image" => $image->id(),
					":read" => $crrRead
				);
				//
				// Updating rate.
				$stmt->execute($params);
				$image->rated = false;
				//
				// At this point, operation is considered to be a
				// success.
				$out = true;
			}
		}
		//
		// Returning update status.
		return $out;
	}
	/**
	 * This method allows to update statistics for all those comics that were
	 * which read status changed and those that have received a rate.
	 *
	 * @return int Returns the amount of comics updated.
	 */
	public function updateStats() {
		//
		// Loading global pointers.
		global $wcDefaults;
		//
		// Setting default value to be returned.
		$out = 0;
		//
		// Checking that Read Only Mode is not activated.
		if(!$wcDefaults["read-only"]) {
			//
			// Creating a statement to update statistics.
			// @{
			$stmt = null;
			$stmtId = __CLASS__."::".__FUNCTION__."[cmc]";
			if($this->_db->has($stmtId)) {
				$stmt = $this->_db->get($stmtId);
			} else {
				$query = "update  {$this->_dbprefix}comics\n";
				$query .= "set     cmc_reads = (\n";
				$query .= "                select  count(1)\n";
				$query .= "                from    {$this->_dbprefix}rr_comics\n";
				$query .= "                where   crr_group     = cmc_group\n";
				$query .= "                 and    crr_comic     = cmc_id\n";
				$query .= "                 and    crr_read      = 'Y'),\n";
				$query .= "        cmc_rate  = (\n";
				$query .= "                select  avg(crr_rate)\n";
				$query .= "                from    {$this->_dbprefix}rr_comics\n";
				$query .= "                where   crr_group     = cmc_group\n";
				$query .= "                 and    crr_comic     = cmc_id),\n";
				$query .= "        cmc_votes = (\n";
				$query .= "                select  count(*)\n";
				$query .= "                from    {$this->_dbprefix}rr_comics\n";
				$query .= "                where   crr_group     = cmc_group\n";
				$query .= "                 and    crr_comic     = cmc_id),\n";
				$query .= "        cmc_rated = 'Y'\n";
				$query .= "where   cmc_rated = 'N'\n";
				$query .= " and    cmc_lost  = 'N'\n";

				$stmt = $this->_db->prepare($stmtId, $query);
			}
			// @}
			//
			// Updating comic ratings.
			$stmt->execute();
			//
			// Getting how many comics where updated.
			$out = $stmt->rowCount();
			//
			// Creating a statement to update statistics.
			// @{
			$stmt = null;
			$stmtId = __CLASS__."::".__FUNCTION__."[img]";
			if($this->_db->has($stmtId)) {
				$stmt = $this->_db->get($stmtId);
			} else {
				$query = "update  {$this->_dbprefix}images\n";
				$query .= "set     img_reads = (\n";
				$query .= "                select  count(1)\n";
				$query .= "                from    {$this->_dbprefix}rr_images\n";
				$query .= "                where   irr_group     = img_group\n";
				$query .= "                 and    irr_image     = img_id\n";
				$query .= "                 and    irr_read      = 'Y'),\n";
				$query .= "        img_rate  = (\n";
				$query .= "                select  avg(irr_rate)\n";
				$query .= "                from    {$this->_dbprefix}rr_images\n";
				$query .= "                where   irr_group     = img_group\n";
				$query .= "                 and    irr_image     = img_id),\n";
				$query .= "        img_votes = (\n";
				$query .= "                select  count(*)\n";
				$query .= "                from    {$this->_dbprefix}rr_images\n";
				$query .= "                where   irr_group     = img_group\n";
				$query .= "                 and    irr_image     = img_id),\n";
				$query .= "        img_rated = 'Y'\n";
				$query .= "where   img_rated = 'N'\n";
				$query .= " and    img_lost  = 'N'\n";

				$stmt = $this->_db->prepare($stmtId, $query);
			}
			// @}
			//
			// Updating image ratings.
			$stmt->execute();
			//
			// Getting how many images where updated.
			$out += $stmt->rowCount();
		}
		//
		// Returning update status.
		return $out;
	}
	//
	// Public class methods.
}
